const easeInBy = (power) => (t) => Math.pow(t, power);
const easeOutBy = (power) => (t) => 1 - Math.abs(Math.pow(t - 1, power));
const easeInOutBy = (power) => (t) =>
  t < 0.5 ? easeInBy(power)(t * 2) / 2 : easeOutBy(power)(t * 2 - 1) / 2 + 0.5;

const easing = {
  linear: (t: number) => t,
  quadIn: easeInBy(2),
  quadOut: easeOutBy(2),
  quadInOut: easeInOutBy(2),
  cubicIn: easeInBy(3),
  cubicOut: easeOutBy(3),
  cubicInOut: easeInOutBy(3),
  quartIn: easeInBy(4),
  quartOut: easeOutBy(4),
  quartInOut: easeInOutBy(4),
  quintIn: easeInBy(5),
  quintOut: easeOutBy(5),
  quintInOut: easeInOutBy(5),
  sineIn: (t: number) => 1 + Math.sin((Math.PI / 2) * t - Math.PI / 2),
  sineOut: (t: number) => Math.sin((Math.PI / 2) * t),
  sineInOut: (t: number) => (1 + Math.sin(Math.PI * t - Math.PI / 2)) / 2,
  bounceOut: (t: number) => {
    const s = 7.5625;
    const p = 2.75;
    if (t < 1 / p) {
      return s * t * t;
    }
    if (t < 2 / p) {
      t -= 1.5 / p;
      return s * t * t + 0.75;
    }
    if (t < 2.5 / p) {
      t -= 2.25 / p;
      return s * t * t + 0.9375;
    }
    t -= 2.625 / p;
    return s * t * t + 0.984375;
  },
  bounceIn: (t: number) => 1 - easing.bounceOut(1 - t),
  bounceInOut: (t: number) =>
    t < 0.5
      ? easing.bounceIn(t * 2) * 0.5
      : easing.bounceOut(t * 2 - 1) * 0.5 + 0.5,
};

export class Tween {
  from: any;
  to: any;
  duration: any;
  delay: any;
  easing: any;
  onStart: any;
  onUpdate: any;
  onFinish: any;
  startTime: any;
  started: boolean;
  finished: boolean;
  timer: null | number;
  keys: {};
  time: number;
  elapsed: any;
  constructor(settings) {
    const { from, to, duration, delay, easing, onStart, onUpdate, onFinish } =
      settings;

    for (const key in from) {
      if (to[key] === undefined) {
        to[key] = from[key];
      }
    }
    for (const key in to) {
      if (from[key] === undefined) {
        from[key] = to[key];
      }
    }

    this.from = from;
    this.to = to;
    this.duration = duration || 500;
    this.delay = delay || 0;
    this.easing = easing || "linear";
    this.onStart = onStart;
    this.onUpdate = onUpdate || function () {};
    this.onFinish = onFinish;
    this.startTime = Date.now() + this.delay;
    this.started = false;
    this.finished = false;
    this.timer = null;
    this.keys = {};
  }

  update() {
    this.time = Date.now();
    // delay some time
    if (this.time < this.startTime) {
      return;
    }
    if (this.finished) {
      return;
    }
    // finish animation
    if (this.elapsed === this.duration) {
      if (!this.finished) {
        this.finished = true;
        // eslint-disable-next-line @typescript-eslint/no-unused-expressions
        this.onFinish && this.onFinish(this.keys);
      }
      return;
    }
    this.elapsed = this.time - this.startTime;
    this.elapsed = this.elapsed > this.duration ? this.duration : this.elapsed;
    for (const key in this.to) {
      this.keys[key] =
        this.from[key] +
        (this.to[key] - this.from[key]) *
          easing[this.easing](this.elapsed / this.duration);
    }
    if (!this.started) {
      // eslint-disable-next-line @typescript-eslint/no-unused-expressions
      this.onStart && this.onStart(this.keys);
      this.started = true;
    }
    this.onUpdate(this.keys);
  }

  start() {
    this.startTime = Date.now() + this.delay;
    const tick = () => {
      this.update();
      if (this.finished) {
        clearInterval(this.timer);
        this.timer = null;
      }
    };

    // 启动定时器
    this.timer = setInterval(tick, 1000 / 30); // 16ms 约等于 60fps 的帧率
  }

  stop() {
    // cancelAnimationFrame(this.timer);
    clearInterval(this.timer);
    this.timer = null;
  }
}
